package com.crimsec.utils.snmp.traptodb;

import com.crimsec.utils.snmp.model.Event;
import com.crimsec.utils.snmp.model.Eventdata;
import com.crimsec.utils.snmp.model.IConstants;
import com.crimsec.utils.snmp.model.Mibent;
import com.crimsec.utils.snmp.model.dao.EventDAO;
import com.crimsec.utils.snmp.model.dao.MibentDAO;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.persistence.EntityManager;
import org.apache.log4j.Logger;

/**
 * $Id$
 *
 * TODO: 1. получить в качестве параметра конструктора List значений созданных в
 * snmptrapd->traphandler->.... 2. разобрать List согластно следуюющей структуре
 * (взято из snmptrapd.conf(5), см. также логи utils.snmp.traptodb.App): The
 * input format is as follows, one entry per line:
 *
 * HOSTNAME The name of the host that sent the notification, as determined by
 * gethostbyaddr(3). <UNKNOWN> если не найдено в DNS
 *
 * IPADDRESS The IP address of the host that sent the notification. строка вида
 * UDP: [192.168.1.212]:29135->[0.0.0.0]:0 (сначала адрес отправителя и порт)
 *
 * VARBINDS A list of variable bindings describing the contents of the
 * notification, one per line. The first token on each line (up until a space)
 * is the OID of the varind, and the remainder of the line is its value. The
 * format of both of these are controlled by the outputOption directive (or
 * similar configuration).
 *
 * The first OID should always be SNMPv2-MIB::sysUpTime.0, and the second should
 * be SNMPv2-MIB::snmpTrapOID.0. The remaining lines will contain the payload
 * varbind list. For SNMPv1 traps, the final OID will be
 * SNMPv2-MIB::snmpTrapEnterprise.0.
 *
 * для протоколов SNMPv1 и SNMPv2 сначала обязательно идут
 * DISMAN-EVENT-MIB::sysUpTimeInstance затем SNMPv2-MIB::snmpTrapOID.0 причем
 * для протокола SNMPv1 generic trap &specific trap перековертируются согласно
 * RFC 2576
 *
 * для протокола SNMPv1 в конце обязательно SNMPv2-MIB::snmpTrapEnterprise.0
 *
 * 2. Для каждой переменной из списка VARBINDS найти ее учетный номер в таблице
 * mibent, если тип не описан, в таблице есть специальная переменная unknown.
 * Для возврата номера лучше написать функцию plsql
 *
 *
 */
public class TraphandleParser {

    private static EntityManager em;
    private static Logger log = Logger.getLogger("traptodb.TraphandleParser");

    public static class MibentFinder {

        public static String clearName(String name) {

            /* TODO: dirty hack
             * сюда могут прийти следующие варианты имен переменных 
             * sysUpTimeInstance простая
             * sysDescr.0  - простая с нулем в конце
             * ifName.1  -   простая с табличным индексом (в данном случае номер интерфейса)
             * 1.3.4.5.7 - цифровая нотация
             * iso.org.dod.internet.a.b.c.dsysName - буквенная нотация
             * iso.2.2.2.2.2.2 - 'смесь' буквенной и цифровой нотации (в случае когда корень переменной известен, а хвост нет)
             * Унивесальный парсер должен обработать все эти случаи
             * но поскольку trapd for freebsd, для известных ему переменных
             * выдает MIBDB-NAME::entity-name[.index]
             * а для неизвестных -'смесь'
             * Для обработки трапов можно остановится на этом dirty
             */
            if (name.endsWith(".0")) { 
                return name.substring(0, name.lastIndexOf("."));
            } else {
                return name;
            }
        }

        public static Mibent parse(String var) {

            Mibent result;
            String[] varparts = var.split("::");
            if (log.isDebugEnabled()) {
                log.debug("var:" + var + ", varparts:" + Arrays.toString(varparts));
            }
            if (varparts.length > 1) { //some like SNMPv2-MIB::sysDescr.0 
                String entname = clearName(varparts[1]);
                result = MibentDAO.findMibentByName(entname, em);
            } else { //some like iso.2.2.2.2.2.2, or sysDescr.0 (without mibdb part)
                String entname = clearName(varparts[0]);
                result = MibentDAO.findMibentByName(entname, em);
            }
            return result;
        }
    }

    public static void setEntityManager(EntityManager emng) {
        em = emng;
    }

    public static Event parse(List<String> traphandlerdata) throws Exception {

        Event event = EventDAO.createTrap(traphandlerdata.remove(0),
                traphandlerdata.remove(0));
        event.setEventdata(parseData(event, traphandlerdata));
        return event;
    }

    public static void save(Event event) throws IOException {
        EventDAO.save(event, em);
    }

    private static List<Eventdata> parseData(Event event, List<String> varbinds) {
        log.debug("varbinds:" + varbinds);
        List<Eventdata> result = new ArrayList<Eventdata>(12);
        for (String varbind : varbinds) {
            /* The first token on each line (up until a space)
             * is the OID of the varind, and the remainder
             * of the line is its value
             */
            String[] bindParts = varbind.split("\\s");
            if (log.isDebugEnabled()) {
                log.debug("varbind:" + varbind + ", bindParts:" + Arrays.toString(bindParts));
            }

            Mibent var = MibentFinder.parse(bindParts[0]);

            StringBuilder dataParts = new StringBuilder();
            if (bindParts.length > 1) {
                for (int i = 1; i < bindParts.length; i++) {
                    dataParts.append(bindParts[i]);
                    dataParts.append(" ");
                }
            }


            if (var.getName().equals(IConstants.unknown)) {
                //i case some like iso.2.2.2.2.2.2(not in found in db),
                // mib-entity name is <unknown>, and value will be set to
                //iso.2.2.2.2.2.2<IConstants.valSeparator>actual value
                StringBuilder buf = new StringBuilder(bindParts[0]/*push full name to value*/);
                buf.append(IConstants.valSeparator);
                buf.append(dataParts);
                dataParts = buf;
                log.debug("dataParts:" + dataParts);
            }
            Eventdata data = EventDAO.createEvdata(event,
                    var,
                    dataParts.toString().trim());

            result.add(data);

        }
        return result;
    }
}
